using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using RimWorld;


namespace Verse{
public class Corpse : ThingWithComps, IThingContainerOwner, IThoughtGiver, IStrippable, IBillGiver
{
	//Config
	private ThingContainer innerContainer;

	//Working vars
	private int timeOfDeath = -1000;
    private int vanishAfterTimestamp = -1000;
    private BillStack operationsBillStack = null;
	public bool everBuriedInSarcophagus;
    
    //Constants
    private const int VanishAfterTicks = 50 * GenDate.TicksPerDay;

	//Properties
	public Pawn InnerPawn
	{
		get
		{
			if( innerContainer.Count > 0 )
				return (Pawn)innerContainer[0];
			else
				return null;
		}
		set
		{
			if( innerContainer.Count > 0 )
			{
				Log.Error("Setting InnerPawn in corpse that already has one.");
				innerContainer.Clear();
			}
			innerContainer.TryAdd(value);
		}
	}
    public int Age{
        get
        {
            return Find.TickManager.TicksGame - timeOfDeath;
        }
        set
        {
            timeOfDeath = Find.TickManager.TicksGame - value;
        }
    }
	public override string Label
	{
		get
		{
			return "DeadLabel".Translate(InnerPawn.LabelCap);
		}
	}
	public override bool IngestibleNow
	{
		get
		{
			if( Bugged )
			{
				Log.Error("IngestibleNow on Corpse while Bugged.");
				return false;
			}

			if( !InnerPawn.RaceProps.IsFlesh )
				return false;

			if( this.GetRotStage() == RotStage.Dessicated )
				return false;

			return true;
		}
	}
	public RotDrawMode CurRotDrawMode
	{
		get
		{
			var rottable = GetComp<CompRottable>();

			if( rottable != null )
			{
				if( rottable.Stage == RotStage.Rotting )
					return RotDrawMode.Rotting;
				else if( rottable.Stage == RotStage.Dessicated )
					return RotDrawMode.Dessicated;
			}

			return RotDrawMode.Fresh;
		}
	}
    private bool ShouldVanish
    {
        get
        {
             return InnerPawn.RaceProps.Animal &&
                    vanishAfterTimestamp > 0 &&
                    Age >= vanishAfterTimestamp &&
					holdingContainer == null &&
                    this.GetRoom().TouchesMapEdge &&
                    !Map.roofGrid.Roofed(Position);
        }
    }
	public override IEnumerable<StatDrawEntry> SpecialDisplayStats
	{
		get
		{
			foreach( var s in base.SpecialDisplayStats )
			{
				yield return s;
			}

			yield return new StatDrawEntry(StatCategoryDefOf.Basics, "Nutrition".Translate(), FoodUtility.GetBodyPartNutrition(InnerPawn, InnerPawn.RaceProps.body.corePart).ToString("0.##"));
		
			var meatAmount = StatDefOf.MeatAmount;
			yield return new StatDrawEntry(meatAmount.category, meatAmount, InnerPawn.GetStatValue(meatAmount), StatRequest.For(InnerPawn));

			var leatherAmount = StatDefOf.LeatherAmount;
			yield return new StatDrawEntry(leatherAmount.category, leatherAmount, InnerPawn.GetStatValue(leatherAmount), StatRequest.For(InnerPawn));
		}
	}
    public BillStack BillStack { get { return operationsBillStack; } }
    public IEnumerable <IntVec3> IngredientStackCells { get { yield return InteractionCell; } }
    public bool Bugged
	{
		get
		{
			//This shouldn't ever happen and is purely a bug mitigation
			return innerContainer.Count == 0 || innerContainer[0] == null;
		}
	}


    public Corpse()
    {
        operationsBillStack = new BillStack(this);
		innerContainer = new ThingContainer(this, oneStackOnly: true, contentsLookMode: LookMode.Reference);
    }
    
    public bool CurrentlyUsable()
    {
        return InteractionCell.IsValid;
    }
    
    public bool AnythingToStrip()
    {
        return InnerPawn.AnythingToStrip();
    }
    
	public ThingContainer GetInnerContainer()
	{
		return innerContainer;
	}

	public IntVec3 GetPosition()
	{
		return PositionHeld;
	}

	public Map GetMap()
	{
		return MapHeld;
	}

	public override void SpawnSetup(Map map)
	{
		if( Bugged )
		{
			Log.Error(this + " spawned in bugged state.");
			return;
		}

		base.SpawnSetup(map);

		if( timeOfDeath < 0 )
			timeOfDeath = Find.TickManager.TicksGame;

		InnerPawn.Rotation = Rot4.South; //Fixes drawing errors

		NotifyColonistBar();
	}

	public override void DeSpawn()
	{
		base.DeSpawn();

		if( !Bugged )
			NotifyColonistBar();
	}

	public override void Destroy(DestroyMode mode = DestroyMode.Vanish)
	{
		Pawn innerPawn = null;
		if( !Bugged )
		{
			innerPawn = InnerPawn; // store the reference before removing him from the container so we can use it later

			NotifyColonistBar();

			// unclaim grave if we have any
			if( innerPawn.ownership != null )
				innerPawn.ownership.UnclaimAll();

			// destroy equipment
			if( innerPawn.equipment != null )
				innerPawn.equipment.DestroyAllEquipment();

			// destroy inventory
			innerPawn.inventory.DestroyAll();

			// destroy apparel
			if( innerPawn.apparel != null )
				innerPawn.apparel.DestroyAll();

			innerContainer.Clear(); //Pawn is already destroyed
		}

		base.Destroy(mode);

		// Recheck if WorldPawns wants to keep the inner pawn now that the corpse has been destroyed
		if( innerPawn != null )
			Find.WorldPawns.DiscardIfUnimportant(innerPawn);
	}

    public override void TickRare()
    {
		base.TickRare();

		if( Bugged )
		{
			Log.Error(this + " has null innerPawn. Destroying.");
			Destroy();
			return;
		}

		InnerPawn.TickRare();

        // reset vanishAfterTimestamp to X days from now if not previously set, or if carcass still fresh
        if (vanishAfterTimestamp < 0 || this.GetRotStage() != RotStage.Dessicated)
            vanishAfterTimestamp = Age + VanishAfterTicks;

        if(ShouldVanish)
            Destroy();
    }

	protected override void IngestedCalculateAmounts(Pawn ingester, float nutritionWanted, out int numTaken, out float nutritionIngested)
	{
		//Determine part to take
		var part = GetBestBodyPartToEat(ingester, nutritionWanted);
		if( part == null )
		{
			Log.Error(ingester + " ate " + this + " but no body part was found. Replacing with core part.");
			part = InnerPawn.RaceProps.body.corePart;
		}

		//Determine the nutrition to gain
		float nut = FoodUtility.GetBodyPartNutrition(InnerPawn, part);

		//Affect this thing
		//If ate core part, remove the whole corpse
		//Otherwise, remove the eaten body part
		if( part == InnerPawn.RaceProps.body.corePart )
		{
			if( PawnUtility.ShouldSendNotificationAbout(InnerPawn) && InnerPawn.RaceProps.Humanlike )
				Messages.Message("MessageEatenByPredator".Translate(InnerPawn.LabelShort, ingester.LabelIndefinite()).CapitalizeFirst(), ingester, MessageSound.Negative);

			numTaken = 1;
		}
		else
		{
			var missing = (Hediff_MissingPart)HediffMaker.MakeHediff(HediffDefOf.MissingBodyPart, InnerPawn, part);
			missing.lastInjury = HediffDefOf.Bite;
			missing.IsFresh = true;
			InnerPawn.health.AddHediff(missing);

			numTaken = 0;
		}

		//Humans have a constant 5% chance of food poisoning from eating unbutchered corpses
		if( ingester.RaceProps.Humanlike && Rand.Value < 0.05f )
			FoodUtility.AddFoodPoisoningHediff(ingester, this);

		nutritionIngested = nut;
	}

	public override IEnumerable<Thing> ButcherProducts( Pawn butcher, float efficiency )
	{
		foreach( var t in InnerPawn.ButcherProducts(butcher, efficiency) )
		{
			yield return t;
		}

		//Spread blood
		if( InnerPawn.RaceProps.BloodDef != null )
            FilthMaker.MakeFilth(butcher.Position, butcher.Map, InnerPawn.RaceProps.BloodDef, InnerPawn.LabelIndefinite() );

		//Thought/tale for butchering humanlike
		if( InnerPawn.RaceProps.Humanlike )
		{
			butcher.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.ButcheredHumanlikeCorpse);
			foreach( var p in butcher.Map.mapPawns.SpawnedPawnsInFaction(butcher.Faction) )
			{
				if( p == butcher || p.needs == null || p.needs.mood == null || p.needs.mood.thoughts == null )
					continue;
				p.needs.mood.thoughts.memories.TryGainMemoryThought(ThoughtDefOf.KnowButcheredHumanlikeCorpse);
			}
			TaleRecorder.RecordTale(TaleDefOf.ButcheredHumanlikeCorpse, butcher);
		}
	}


	public override void ExposeData()
	{
		base.ExposeData();

		Scribe_Values.LookValue( ref timeOfDeath, "timeOfDeath" );
        Scribe_Values.LookValue(ref vanishAfterTimestamp, "vanishAfterTimestamp");
		Scribe_Values.LookValue(ref everBuriedInSarcophagus, "everBuriedInSarcophagus");
        Scribe_Deep.LookDeep( ref operationsBillStack, "operationsBillStack", this );
        Scribe_Deep.LookDeep( ref innerContainer, "innerContainer", this );
	}

	public void Strip()
    {
        if(InnerPawn.equipment != null)
            InnerPawn.equipment.DropAllEquipment(PositionHeld, false);

        if(InnerPawn.apparel != null)
            InnerPawn.apparel.DropAll(PositionHeld, false);

        if(InnerPawn.inventory != null)
            InnerPawn.inventory.DropAllNearPawn(PositionHeld);
    }

	public override void DrawAt(Vector3 drawLoc)
	{
		//Don't draw in graves
		Building storeBuilding = this.StoringBuilding();
		if( storeBuilding != null && storeBuilding.def == ThingDefOf.Grave )
			return;

		InnerPawn.Drawer.renderer.RenderPawnAt( drawLoc, CurRotDrawMode );
	}

	public Thought_Memory GiveObservedThought()
	{
		//Non-humanlike corpses never give thoughts
		if( !InnerPawn.RaceProps.Humanlike )
			return null;

        Thing storingBuilding = this.StoringBuilding();
		if( storingBuilding == null )
		{
			//Laying on the ground
            
			Thought_MemoryObservation obs;
			if( this.IsNotFresh() )
				obs = (Thought_MemoryObservation)ThoughtMaker.MakeThought(ThoughtDefOf.ObservedLayingRottingCorpse);
            else
				obs = (Thought_MemoryObservation)ThoughtMaker.MakeThought(ThoughtDefOf.ObservedLayingCorpse);
			obs.Target = this;
			return obs;
		}
        
		return null;
	}

	public override string GetInspectString()
	{
		System.Text.StringBuilder sb = new System.Text.StringBuilder();
		if( InnerPawn.Faction != null )
			sb.AppendLine("Faction".Translate() + ": " + InnerPawn.Faction);
		sb.AppendLine("DeadTime".Translate( Age.ToStringTicksToPeriod(false) ) );

		float percentMissing = 1f - InnerPawn.health.hediffSet.GetCoverageOfNotMissingNaturalParts(InnerPawn.RaceProps.body.corePart);

		if( percentMissing != 0f )
		{
			sb.AppendLine("CorpsePercentMissing".Translate() + ": " + percentMissing.ToStringPercent());
		}

		sb.AppendLine(base.GetInspectString());
		return sb.ToString();
	}

	public void RotStageChanged()
	{
		PortraitsCache.SetDirty(InnerPawn);
		NotifyColonistBar();
	}

	private BodyPartRecord GetBestBodyPartToEat(Pawn ingester, float nutritionWanted)
	{
		var candidates = InnerPawn.health.hediffSet.GetNotMissingParts()
			.Where(x => x.depth == BodyPartDepth.Outside && FoodUtility.GetBodyPartNutrition(InnerPawn, x) > 0.001f);

		if( !candidates.Any() )
			return null;

		// get part which nutrition is the closest to what we want
		return candidates.MinBy(x => Mathf.Abs(FoodUtility.GetBodyPartNutrition(InnerPawn, x) - nutritionWanted));
	}

	private void NotifyColonistBar()
	{
		if( InnerPawn.Faction == Faction.OfPlayer && Current.ProgramState == ProgramState.Playing )
			Find.ColonistBar.MarkColonistsDirty();
	}
}}
